﻿using BCCommon;
using BCCommon.Enums;
using BCCommon.Extensions;
using BCDto.Common.ErrorMessage;
using BCDto.Common.SystemExceptionRecord;
using BCDto.WebSocketComponent;
using BCService.Common.ExceptionService;
using BCWebSocket;
using BCWebSocket.Custom;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BCTMSRESTful.Configs
{
    /// <summary>
    /// WebSocket处理
    /// </summary>
    public class WebSocketCustomHandler
    {
        private readonly ISystemExceptionRecordService exceptionRecordService;
        private readonly IWebSocketCustomService webSocketCustomService;
        private readonly IRedisService redisService;

        private static ConcurrentDictionary<string, WebSocketUserDto> currentSockets = new ConcurrentDictionary<string, WebSocketUserDto>();
        private static Dictionary<string, string> websocketKeyValue = new Dictionary<string, string>();

        private const int BUFFERSIZE = 4096;
        private string webSocketKey;
        private WebSocket webSocket;
        private CancellationToken ct;
        private readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = false } },
            Converters = new List<JsonConverter> {
                new StringEnumConverter { AllowIntegerValues = true },
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeLocal, DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }
            },
            NullValueHandling = NullValueHandling.Ignore
        };
        public WebSocketCustomHandler(ISystemExceptionRecordService exceptionRecordService,
            IWebSocketCustomService webSocketCustomService,
            IRedisService redisService)
        {
            this.exceptionRecordService = exceptionRecordService;
            this.webSocketCustomService = webSocketCustomService;
            this.redisService = redisService;
        }

        //创建链接  
        public async Task Connect(HttpContext context)
        {
            //执行接收  
            WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
            ct = context.RequestAborted;
            ICollection<string> keys = context.Request.Query.Keys;
            this.webSocketKey = context.Request.Headers["Sec-WebSocket-Key"];
            if (!keys.Contains("uid"))
            {
                await socket.SendAsync(this.GetMessageContent("当前用户Id不存在。"), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            string userId = context.Request.Query["uid"];
            if (!currentSockets.ContainsKey(userId))
            {
                WebSocketUserDto webSocketUserDto = new WebSocketUserDto()
                {
                    UserId = userId,
                    LastLogonTime = DateTime.Now,
                    LastBeatTime = DateTime.Now,
                    WebSocket = socket
                };
                if (!currentSockets.TryAdd(userId, webSocketUserDto))
                {
                    await socket.SendAsync(this.GetMessageContent("WebSocket保存失败。"), WebSocketMessageType.Text, true, CancellationToken.None);
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "WebSocket保存失败，正常关闭连接。", CancellationToken.None);
                }
                websocketKeyValue.Add(this.webSocketKey, userId);
            }
            else
            {
                WebSocketUserDto webSocketUserDto = currentSockets.GetValueOrDefault(userId);
                if (webSocketUserDto == default(WebSocketUserDto))
                {
                    await socket.SendAsync(this.GetMessageContent("此用户回话已过期，请重新连接。"), WebSocketMessageType.Text, true, CancellationToken.None);
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "此用户回话已过期，请重新连接。", CancellationToken.None);
                }
                WebSocketUserDto newWebSocketUserDto = webSocketUserDto;
                newWebSocketUserDto.WebSocket.Dispose();
                newWebSocketUserDto.WebSocket = socket;
                if (!currentSockets.TryUpdate(userId, newWebSocketUserDto, webSocketUserDto))
                {
                    await socket.SendAsync(this.GetMessageContent("更新WebSocket失败。"), WebSocketMessageType.Text, true, CancellationToken.None);
                    await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "更新WebSocket失败，正常关闭连接。", CancellationToken.None);
                }

                if (websocketKeyValue.ContainsValue(userId))
                {
                    websocketKeyValue.Remove(websocketKeyValue.Single(p => p.Value.Equals(userId)).Key);
                    websocketKeyValue.Add(this.webSocketKey, userId);
                }
                else
                {
                    websocketKeyValue.Add(this.webSocketKey, userId);
                }
            }
            if (socket.State != WebSocketState.CloseSent && socket.State != WebSocketState.Closed)
            {
                this.webSocket = socket;
                //执行监听  
                await EchoLoop();
                WriteLog(string.Format("{0} 退出了EchoLoop", userId));
            }
        }
        /// <summary>  
        /// 响应处理  
        /// </summary>  
        /// <returns></returns>  
        async Task EchoLoop()
        {
            string userId = WebSockeCollection.WebsocketKeyValue[this.webSocketKey];
            try
            {
                //Console.WriteLine("建立了一个连接");
                var buffer = new byte[BUFFERSIZE];
                //ReceiveAsync是阻塞方法，会让出CPU资源
                var seg = new ArraySegment<byte>(buffer);
                WebSocketReceiveResult incoming = await this.webSocket.ReceiveAsync(seg, ct);
                while (!incoming.CloseStatus.HasValue && this.webSocket.State == WebSocketState.Open)
                {

                    if (incoming.MessageType == WebSocketMessageType.Text)
                    {
                        string acceptStr = string.Empty;
                        acceptStr = Encoding.UTF8.GetString(seg.Array).Trim(char.MinValue);
                        if (!string.IsNullOrWhiteSpace(acceptStr))
                        {
                            try
                            {
                                string userOffline = "当前用户不在线，请稍后再试。";
                                string customOffline = "当前客服不在线，请稍后再试。";
                                WebSocketRequestDto request = JsonConvert.DeserializeObject<WebSocketRequestDto>(acceptStr, JsonSerializerSettings);
                                if (request.ChatType == ChatType.Heartbeats)
                                {
                                    //这里是心跳探针，无需处理
                                }
                                else if (request.ChatType == ChatType.LeaveMessage)
                                {
                                    WebSocketResponseDto webSocketResponseDto = this.webSocketCustomService.LeaveMessage(request);
                                    await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                }
                                else if (request.ChatType == ChatType.Custom || request.ChatType == ChatType.User)
                                {
                                    WebSocketUserDto toUserWebSocketDto;

                                    if (request.ChatOperationType.HasValue)
                                    {
                                        WebSocketResponseDto webSocketResponseDto = this.webSocketCustomService.OperationHandle(request);
                                        if (webSocketResponseDto == default(WebSocketResponseDto))
                                        {
                                            throw new ArgumentException("系统异常，请重试!");
                                        }
                                        if (webSocketResponseDto.ChatType == ChatType.Custom && webSocketResponseDto.ChatOperationType == ChatOperationType.Logon)
                                        {
                                            this.webSocketCustomService.Online(WebSockeCollection.CurrentSockets, webSocketResponseDto.ToUser);
                                            if (this.webSocket.State == WebSocketState.CloseReceived || this.webSocket.State == WebSocketState.Open)
                                            {
                                                await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                            }
                                            else
                                            {
                                                if (!WebSockeCollection.CurrentSockets.Remove(userId, out WebSocketUserDto webSocket))
                                                {
                                                    throw new ArgumentException("websocket已退出" + userId);
                                                }
                                            }
                                        }
                                        else if (request.ChatOperationType.Value == ChatOperationType.TakeCustom)
                                        {
                                            string errorMessage = string.Empty;

                                            bool formUser = !request.FromUser.Contains("cus_");
                                            if (request.ChatType == ChatType.Custom)
                                            {
                                                errorMessage = !webSocketResponseDto.ToUser.Contains("cus_") ? userOffline : customOffline;
                                            }
                                            if (string.IsNullOrWhiteSpace(webSocketResponseDto.FromUser))
                                            {
                                                webSocketResponseDto.ChatType = ChatType.LeaveMessage;
                                                webSocketResponseDto.FromUser = null;
                                                webSocketResponseDto.ContentString = errorMessage;
                                            }
                                            else if (!WebSockeCollection.CurrentSockets.TryGetValue(webSocketResponseDto.FromUser, out toUserWebSocketDto))
                                            {
                                                if (webSocketResponseDto.FromUser.Contains("cus_"))
                                                {
                                                    this.webSocketCustomService.CustomOffline(webSocketResponseDto.FromUser);
                                                }
                                                webSocketResponseDto.ChatType = ChatType.LeaveMessage;
                                                webSocketResponseDto.FromUser = null;
                                                webSocketResponseDto.ContentString = errorMessage;
                                            }
                                            else
                                            {
                                                WebSocketResponseDto systemSendMessage = new WebSocketResponseDto()
                                                {
                                                    FromUser = request.FromUser,
                                                    ToUser = webSocketResponseDto.FromUser,
                                                    ChatType = ChatType.Custom,
                                                    ChatOperationType = ChatOperationType.TakeCustom
                                                };
                                                if (toUserWebSocketDto.WebSocket.State != WebSocketState.Open)
                                                {
                                                    webSocketResponseDto.ChatType = ChatType.LeaveMessage;
                                                    webSocketResponseDto.FromUser = null;
                                                    webSocketResponseDto.ContentString = errorMessage;
                                                }
                                                else
                                                {
                                                    await toUserWebSocketDto.WebSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(systemSendMessage, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                                }
                                            }
                                            await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                        }
                                    }
                                    else if (request.ChatType == ChatType.Custom)
                                    {
                                        if (string.IsNullOrWhiteSpace(request.ToUser) || request.ToUser.Equals("undefined"))
                                        {
                                            if (!string.IsNullOrWhiteSpace(request.FromUser) || !request.FromUser.Equals("undefined"))
                                            {
                                                WebSocketResponseDto webSocketResponseDto = new WebSocketResponseDto();
                                                webSocketResponseDto.ChatType = ChatType.Custom;
                                                webSocketResponseDto.FromUser = null;
                                                webSocketResponseDto.ErrorMessage = "未找到消息接收者，请重试!";
                                                webSocketResponseDto.ErrorCode = "000003";
                                                await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                            }
                                            else
                                            {
                                                throw new ArgumentException("消息接收者不能为空!");
                                            }
                                        }
                                        if (!WebSockeCollection.CurrentSockets.TryGetValue(request.ToUser, out toUserWebSocketDto))
                                        {
                                            if (request.FromUser.Contains("cus_"))
                                            {
                                                this.webSocketCustomService.Offline(WebSockeCollection.CurrentSockets, request.ToUser, true);
                                            }
                                            else
                                            {
                                                WebSocketResponseDto webSocketResponseDto = new WebSocketResponseDto();
                                                webSocketResponseDto.ChatType = ChatType.LeaveMessage;
                                                webSocketResponseDto.FromUser = null;
                                                webSocketResponseDto.ContentString = customOffline;
                                                webSocketResponseDto.ToUser = request.FromUser;
                                                await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                            }
                                        }
                                        else
                                        {
                                            WebSocketResponseDto webSocketResponseDto = this.webSocketCustomService.ContentHandle(request);
                                            if (toUserWebSocketDto.WebSocket.State != WebSocketState.Open)
                                            {
                                                webSocketResponseDto.ChatType = ChatType.Custom;
                                                webSocketResponseDto.FromUser = null;
                                                webSocketResponseDto.ErrorMessage = "用户已下线";
                                                webSocketResponseDto.ErrorCode = "000002";
                                                await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                            }
                                            else
                                            {

                                                await toUserWebSocketDto.WebSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(webSocketResponseDto, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
                                            }
                                        }
                                    }
                                }
                                //TODO:关于系统提醒类型的将来实现
                            }
                            catch (ArgumentException ex)
                            {
                                await WriteException(ex, acceptStr);
                            }
                            catch (WebSocketException ex)
                            {

                                await WriteException(ex, acceptStr);
                            }
                        }
                    }
                    else if (incoming.MessageType == WebSocketMessageType.Binary)
                    {
                        //TODO:websocket发送非文本信息的二进制字节流数据处理将来实现
                    }
                    else
                    {
                        //TODO:未知逻辑，待补充
                    }
                    buffer = new byte[BUFFERSIZE];
                    seg = new ArraySegment<byte>(buffer);
                    try
                    {
                        if (this.webSocket.State == WebSocketState.Open)
                        {
                            incoming = await this.webSocket.ReceiveAsync(seg, ct);
                        }
                        else
                        {
                            this.webSocket.Dispose();
                            break;
                        }
                    }
                    catch (OperationCanceledException oce)
                    {
                        if (oce.CancellationToken.IsCancellationRequested)
                        {
                            this.webSocket.Abort();
                            break;
                        }
                    }
                }
                if (this.webSocket.State != WebSocketState.Open)
                {
                    this.webSocket.Dispose();
                    return;
                }
                WebSocketState[] webSocketStates = new WebSocketState[] { WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent };
                if (!webSocket.CloseStatus.HasValue)
                {
                    await this.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct);
                }
                else if (webSocket.State.In(webSocketStates))
                {
                    await this.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct);
                }
            }
            catch (Exception ex)
            {
                await WriteException(ex, this.webSocketKey + string.Format("********未处理异常：userId:{0}", userId));
            }
            finally
            {
                WriteLog(string.Format("{0} 退出了", userId));
                this.RedistributeCustom(userId);
                WebSockeCollection.WebsocketKeyValue.Remove(this.webSocketKey);
            }
        }
        /// <summary>
        /// 重新分配客服
        /// </summary>
        /// <param name="userId">webSocket连接者Id</param>
        /// <returns></returns>
        private async void RedistributeCustom(string userId)
        {
            try
            {
                if (userId.Contains("cus_"))
                {
                    this.webSocketCustomService.RedistributeCustom(currentSockets, userId);
                }
                else
                {
                    this.webSocketCustomService.Offline(currentSockets, userId, false);
                }
            }
            catch (Exception ex)
            {
                WriteLog(string.Format("{0} 退出了:RedistributeCustom里的Exception", userId));
                await WriteException(ex, "未处理异常");
            }
        }
        /// <summary>
        /// 记录异常信息
        /// </summary>
        /// <param name="ex">异常信息</param>
        /// <param name="requestJson">请求Json</param>
        /// <returns></returns>
        private async Task WriteException(Exception ex, string requestJson)
        {
            //if (ex is ArgumentException || ex is WebSocketException)
            //{
            //    await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(new ErrorMessageDto() { ErrorCode = "99999998", ErrorMessage = ex.Message }, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
            //}
            //else
            //{
            SystemExceptionRecordDto exceptionDto = new SystemExceptionRecordDto
            {
                Name = ex.GetType().Name,
                HelpLink = ex.HelpLink,
                Message = ex.Message,
                StackTrace = string.Format("{0}\r\n{1}", ex.StackTrace, requestJson),
                HResult = ex.HResult,
                Data = JObject.FromObject(ex.Data).ToString(),
                CreateTime = DateTime.Now
            };
            string source = string.Empty;
            exceptionDto.InnerException = ex.InnerException?.Message;
            exceptionDto.Source = string.IsNullOrWhiteSpace(source) ? ex.Source : source;
            this.exceptionRecordService.Create(exceptionDto);
            await this.webSocket.SendAsync(GetMessageContent(JsonConvert.SerializeObject(new ErrorMessageDto() { ErrorCode = "99999998", ErrorMessage = ex.Message }, JsonSerializerSettings)), WebSocketMessageType.Text, true, ct);
            //}
        }
        /// <summary>
        /// 转换消息为字节流信息
        /// </summary>
        /// <param name="message">消息内容</param>
        /// <returns></returns>
        private ArraySegment<byte> GetMessageContent(string message)
        {
            byte[] backInfo = UTF8Encoding.Default.GetBytes(message);
            var outgoing = new ArraySegment<byte>(backInfo, 0, backInfo.Length);
            return outgoing;
        }
        /// <summary>
        /// 写入日志
        /// </summary>
        /// <param name="message"></param>
        private void WriteLog(string message)
        {
            FileMode mode = FileMode.Append;
            FileInfo file = new FileInfo(string.Format("{1}{0}{2}", Path.DirectorySeparatorChar, Directory.GetCurrentDirectory(), "websocket.log"));

            if (!file.Exists)
            {
                mode = FileMode.OpenOrCreate;
            }
            using (FileStream fs = new FileStream(string.Format("{1}{0}{2}", Path.DirectorySeparatorChar, Directory.GetCurrentDirectory(), "websocket.log"), mode))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    sw.WriteLine(message);
                }
            }
        }
    }
}
